cmake_minimum_required(VERSION 3.10)

# 注：一般不修改这个文件，以保持通用

# ------------------------- 工程名称（默认取所在目录名称）-----------------------------
file(RELATIVE_PATH PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR}/.. ${CMAKE_CURRENT_LIST_DIR})
project(${PROJECT_NAME})


# ---------------------------- 自定义配置 --------------------------------
include(config.cmake OPTIONAL)


# ---------------------------- 通用map实现（提供mag_set map_get map_entry宏） --------------------------------

set(MAP_SEP "@")  # 键值对分隔符

# 设置map键值对
macro(map_set map_var key val)    
    set(_tmp "")
    foreach(p ${${map_var}})
        if(NOT p MATCHES "^${key}${MAP_SEP}")
            set(_tmp "${_tmp};${p}")
        endif()
    endforeach()    
    set(${map_var} "${_tmp};${key}${MAP_SEP}${val}")
endmacro()

# 获取MAP值（不存在则返回空）
macro(map_get map_var key out)
    set(${out} "")
    foreach(p ${${map_var}})
        if(p MATCHES "^${key}${MAP_SEP}(.*)$")
            set(${out} "${CMAKE_MATCH_1}")
            break()  
        endif()
    endforeach()
endmacro()

# 从map的单个项中提取键和值
macro(map_entry entry key_var value_var)
    if(${entry} MATCHES "^(.*)${SEP}(.*)$")
        set(${key_var} "${CMAKE_MATCH_1}")
        set(${value_var} "${CMAKE_MATCH_2}")
    else()
        # 处理无效项
        set(${key_var} "")
        set(${value_var} "")
    endif()
endmacro()




# ---------------------------- 通用函数：识别文件include --------------------------------

function(read_source_includes filename user_headers sys_headers)
    # 初始化输出变量
    set(u_headers "")
    set(s_headers "")
    
    # 读取文件内容
    file(READ "${filename}" file_content)
    
    # 一次性提取所有 #include 语句（无论引号类型）
    string(REGEX MATCHALL "#include [\"<][^\">]+[\">]" all_matches "${file_content}")
    
    # 遍历所有匹配项，区分引号和尖括号类型
    foreach(match ${all_matches})
        # 处理双引号引用的头文件
        if(match MATCHES "#include \"([^\"]+)\"")
            list(APPEND u_headers "${CMAKE_MATCH_1}")
        # 处理尖括号引用的头文件
        elseif(match MATCHES "#include <([^>]+)>")
            list(APPEND s_headers "${CMAKE_MATCH_1}")
        endif()
    endforeach()
    
    # 移除重复项
    list(REMOVE_DUPLICATES u_headers)
    list(REMOVE_DUPLICATES s_headers)
    
    # 传递结果到父作用域
    set(${user_headers} "${u_headers}" PARENT_SCOPE)
    set(${sys_headers} "${s_headers}" PARENT_SCOPE)
endfunction()




# -------------------------- 查找所有头文件添加到include目录（保存变量 HEADER_LIST） -----------------------------
file(GLOB_RECURSE HEADER_LIST *.h *.hpp)
list(FILTER HEADER_LIST EXCLUDE REGEX ".*/CMakeFiles/.*")
foreach(FILENAME IN LISTS HEADER_LIST)
	get_filename_component(DIRNAME ${FILENAME} DIRECTORY)	
	if(NOT "${DIRNAME}" IN_LIST INCS)
		list(APPEND INCS ${DIRNAME})		
	endif()
endforeach()
foreach(INC IN LISTS INCS)
	include_directories(${INC})
endforeach()
#message("HEADER_LIST = ${HEADER_LIST}")

# -------------------------- 查找所有源码文件（保存变量 SOURCE_LIST） -----------------------------
file(GLOB_RECURSE SOURCE_LIST *.c *.cpp)
list(FILTER SOURCE_LIST EXCLUDE REGEX ".*/CMakeFiles/.*")


# ---------------------------- 建立头文件名-路径映射关系（保存变量 HEADER_MAP） --------------------------------
foreach(FILENAME IN LISTS HEADER_LIST)
	get_filename_component(FNAME ${FILENAME} NAME)	
	map_set(HEADER_MAP ${FNAME} ${FILENAME})
endforeach()


# ---------------------------- 建立源文件名-路径映射关系（保存变量 SOURCE_MAP） --------------------------------
foreach(FILENAME IN LISTS SOURCE_LIST)
	get_filename_component(FNAME ${FILENAME} NAME)	
	map_set(SOURCE_MAP ${FNAME} ${FILENAME})
endforeach()



#----------------- 编译Linux结果不依赖 libstdc++.so libgcc_s.so -------------------------
if(NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
	if(CMAKE_C_COMPILER_ID MATCHES "GNU")
		if(NOT EXISTS ${CMAKE_BINARY_DIR}/libstdc++.a)
			execute_process(COMMAND ${CMAKE_CXX_COMPILER} -print-file-name=libstdc++.a OUTPUT_VARIABLE LIBSTDCXXPATH OUTPUT_STRIP_TRAILING_WHITESPACE)
			execute_process(COMMAND ln -s ${LIBSTDCXXPATH} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")			
		endif()
		link_libraries(-L. -static-libgcc)
	endif()
endif()


# --------------------------- 参与编译的源码变量 --------------------------
set(ALL_SOURCE "")


# ---------------------------- 自动识别依赖方式添加源码 ( 使用 add_source(源码文件) ) --------------------------------
set(ADDED_SOURCE "")
set(ADDED_HEADER "")

# 添加一个源码文件，包括include依赖的模块
macro(add_source full_filename)    
    get_filename_component(FNAME ${full_filename} NAME)
	map_get(ADDED_SOURCE ${FNAME} added)
	if(NOT added)
	    map_set(ADDED_SOURCE ${FNAME} 1)
	    #message("add source ${full_filename}")
	    list(APPEND ALL_SOURCE "${full_filename}")		    
        read_source_includes("${full_filename}" user_incs sys_incs)

        # 双引号的头文件，添加编译源码
        foreach(head_name IN LISTS user_incs)
	    	add_header("${head_name}")
	    endforeach()

        # 尖括号的头文件，添加链接库
        #foreach(head_name IN LISTS sys_incs)           
	    #	add_lib("${head_name}")
	    #endforeach()
	endif()
endmacro()

# 添加一个源码目录
macro(add_dir full_dirname)
	foreach(FILENAME IN LISTS SOURCE_LIST)
		if("${FILENAME}" MATCHES "^${full_dirname}")
			add_source("${FILENAME}")
		endif()
	endforeach()
endmacro()


# 添加一个头文件
macro(add_header headname)	
	map_get(ADDED_SOURCE ${headname} added)
	if(NOT added)
		map_set(ADDED_SOURCE ${headname} 1)
		#message("add header ${headname}")
		map_get(HEADER_MAP ${headname} header_fullname)	
		get_filename_component(header_dir "${header_fullname}" DIRECTORY)
		get_filename_component(header_basename "${headname}" NAME_WE)
		
		set(c_file "${header_dir}/${header_basename}.c")
		if(EXISTS "${c_file}")
			add_source("${c_file}")
		endif()
	
		set(cxx_file "${header_dir}/${header_basename}.cpp")
		if(EXISTS "${cxx_file}")
			add_source("${cxx_file}")
		endif()
	
		set(dir "${header_dir}/${header_basename}")
		if(EXISTS "${dir}")
			add_dir("${dir}")
		endif()
	endif()	
endmacro()


# ------------------------- 编译选项设置 -----------------------------
set(CMAKE_CXX_STANDARD 17)


# ------------------------- 导出compile_commands.json文件 -----------------------------

set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)



# ------------------------- 按入口文件添加源码方式 -----------------------------

if(ENTRY_FILE)
    file(TO_CMAKE_PATH ${ENTRY_FILE} ENTRY_FILE)    
    message("-- Entry File: ${ENTRY_FILE}")
    add_source(${ENTRY_FILE})
endif()


# ------------------------- 添加全部源码方式 -----------------------------

if(NOT ENTRY_FILE)
    list(FILTER SOURCE_LIST EXCLUDE REGEX "_test.*")
    set(ALL_SOURCE ${SOURCE_LIST})
endif()





# ------------------------- 生成VS工程时包含分组目录 -----------------------------
if(MSVC)
    set(ALL_SRC_AND_HEAD ${ALL_SOURCE} ${HEADER_LIST})
	foreach(FILENAME IN LISTS ALL_SRC_AND_HEAD)
		get_filename_component(DIRNAME ${FILENAME} DIRECTORY)	
		if(NOT "${DIRNAME}" IN_LIST ALL_SRC_DIRS)
			list(APPEND ALL_SRC_DIRS ${DIRNAME})		
		endif()
	endforeach()
	foreach(INC IN LISTS ALL_SRC_DIRS)		
		file(RELATIVE_PATH GROUP_NAME ${CMAKE_CURRENT_LIST_DIR} ${INC})
		if(NOT "${GROUP_NAME}" STREQUAL "")
			source_group(${GROUP_NAME} ${GROUP_NAME} )
		endif()
	endforeach()
endif()


# ------------------------- 生成VS特殊编译选项 -----------------------------
if(MSVC)
	add_compile_options(/utf-8) # 支持UTF8源码
	add_compile_options(/wd4244 /wd4267)  # 去除警告：更大范围的数字类型 赋值 给更小范围的数字类型
endif()



# ------------------------- 编译可执行程序 -----------------------------
add_executable(${PROJECT_NAME} ${ALL_SOURCE})



# ------------------------- 生成VS工程时包含头文件 -----------------------------
if(MSVC)
	target_sources(${PROJECT_NAME} PRIVATE ${HEADER_LIST})
endif()

